#include "njm_texture.hpp"
#include <cassert>
namespace njm
{

    NjmTexture::NjmTexture(NjmDevice &device, 
        const std::string &name, const std::shared_ptr<NjmImg> &img)
        : device{device}, name{name}, img{img}
    {
        createTextureImage();
        createTextureImageView();
        createTextureSampler();
    }
    NjmTexture::~NjmTexture()
    {
        assert(device.device() != VK_NULL_HANDLE && "texture's device is VK_NULL_HANDLE");
        if (sampler != VK_NULL_HANDLE)  
        {
            vkDestroySampler(device.device(), sampler, nullptr);
        }
        if (imageView != VK_NULL_HANDLE)
        {
            vkDestroyImageView(device.device(), imageView, nullptr);
        }
        if (image != VK_NULL_HANDLE)
        {
            vkDestroyImage(device.device(), image, nullptr);
        }
        if (memory != VK_NULL_HANDLE)
        {
            vkFreeMemory(device.device(), memory, nullptr);
        }
    }

    void NjmTexture::createTextureImage()
    {
        NjmBuffer stagingBuffer{
            device,
            img->data.size(),
            1,
            VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
        };
        stagingBuffer.map();
        stagingBuffer.writeToBuffer(img->data.data());
        stagingBuffer.flush();

        VkImageCreateInfo imageInfo = {};
        imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
        imageInfo.imageType = getImageType();
        imageInfo.extent.width = img->getVkExtent().width;
        imageInfo.extent.height = img->getVkExtent().height;
        imageInfo.extent.depth = 1;
        imageInfo.mipLevels = 1;
        imageInfo.arrayLayers = 1;
        imageInfo.format = img->getVkFormat();
        imageInfo.tiling = getImageTiling();
        imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        imageInfo.usage = getImageUsage();
        imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
        imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
        imageInfo.flags = 0;
        device.createImageWithInfo(imageInfo, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, image, memory);
        device.copyBufferToImage(stagingBuffer.getBuffer(), image, img->getVkExtent().width, img->getVkExtent().height);
    }

    void NjmTexture::createTextureImageView()
    {
        VkImageViewCreateInfo viewInfo = {};
        viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        viewInfo.image = image;
        viewInfo.viewType = getImageViewType();
        viewInfo.format = img->getVkFormat();
        viewInfo.subresourceRange.aspectMask = getImageAspect();
        viewInfo.subresourceRange.baseMipLevel = 0;
        viewInfo.subresourceRange.levelCount = 1;
        viewInfo.subresourceRange.baseArrayLayer = 0;
        viewInfo.subresourceRange.layerCount = 1;
        if (vkCreateImageView(device.device(), &viewInfo, nullptr, &imageView) != VK_SUCCESS) {
            throw std::runtime_error("failed to create texture's imageview!");
        }
    }

    void NjmTexture::createTextureSampler()
    {
        VkSamplerCreateInfo samplerInfo = {};
        samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
        samplerInfo.magFilter = VK_FILTER_LINEAR;
        samplerInfo.minFilter = VK_FILTER_LINEAR;
        samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
        samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
        samplerInfo.maxAnisotropy = 1.0f;
        samplerInfo.compareOp = VK_COMPARE_OP_NEVER;
        samplerInfo.minLod = 0.0f;
        samplerInfo.maxLod = 0.0f;
        samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
        if (vkCreateSampler(device.device(), &samplerInfo, nullptr, &sampler) != VK_SUCCESS) {
            throw std::runtime_error("failed to create texture's sampler!");
        }
    }

    VkDescriptorImageInfo NjmTexture::getCombinedDescriptor() const
    {
        VkDescriptorImageInfo descriptorInfo = {};
        descriptorInfo.sampler = sampler;
        descriptorInfo.imageView = imageView;
        descriptorInfo.imageLayout = getImageLayout();
        return descriptorInfo;
    }

    VkDescriptorImageInfo NjmTexture::getImageDescriptor() const
    {
        VkDescriptorImageInfo descriptorInfo = {};
        descriptorInfo.sampler = VK_NULL_HANDLE;
        descriptorInfo.imageView = imageView;
        descriptorInfo.imageLayout = getImageLayout();
        return descriptorInfo;
    }

    VkDescriptorImageInfo NjmTexture::getSamplerDescriptor() const
    {
        VkDescriptorImageInfo descriptorInfo = {};
        descriptorInfo.sampler = sampler;
        descriptorInfo.imageView = VK_NULL_HANDLE;
        descriptorInfo.imageLayout = getImageLayout();
        return descriptorInfo;
    }
}
